#!/usr/bin/env python
"""
系统性能监控可视化工具 - 将收集的性能指标可视化展示
"""

import os
import sys
import json
import argparse
import datetime
import glob
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from pathlib import Path
import logging

# 添加项目根目录到Python路径
BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.append(str(BASE_DIR))

# 设置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler(os.path.join(BASE_DIR, 'logs', 'visualization.log')),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger('visualization')

# 确保日志和图表目录存在
os.makedirs(os.path.join(BASE_DIR, 'logs'), exist_ok=True)
os.makedirs(os.path.join(BASE_DIR, 'metrics', 'charts'), exist_ok=True)

class MetricsVisualizer:
    """系统性能指标可视化类"""
    
    def __init__(self, metrics_file=None, output_dir=None):
        """初始化可视化工具"""
        self.metrics_file = metrics_file
        
        if not output_dir:
            self.output_dir = os.path.join(BASE_DIR, 'metrics', 'charts')
        else:
            self.output_dir = output_dir
            os.makedirs(output_dir, exist_ok=True)
        
        # 设置图表样式
        plt.style.use('ggplot')
        plt.rcParams['figure.figsize'] = (12, 8)
        plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
        plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    
    def load_metrics(self):
        """加载性能指标数据"""
        if not self.metrics_file:
            # 如果未指定文件，则使用最新的指标文件
            metrics_files = glob.glob(os.path.join(BASE_DIR, 'metrics', 'system_metrics_*.json'))
            if not metrics_files:
                logger.error("未找到性能指标文件")
                return None
            
            self.metrics_file = max(metrics_files, key=os.path.getctime)
            logger.info(f"使用最新的指标文件: {self.metrics_file}")
        
        try:
            with open(self.metrics_file, 'r') as f:
                metrics = json.load(f)
            
            if not metrics:
                logger.error("指标文件为空")
                return None
            
            logger.info(f"成功加载 {len(metrics)} 条指标记录")
            return metrics
        except Exception as e:
            logger.error(f"加载指标文件失败: {e}")
            return None
    
    def prepare_dataframe(self, metrics):
        """将指标数据转换为Pandas DataFrame格式"""
        # 创建一个包含所有时间点的列表
        df_data = []
        
        for entry in metrics:
            record = {
                'timestamp': pd.to_datetime(entry['timestamp']),
                'cpu_avg_load': entry['system']['cpu']['avg_load'],
                'memory_percent': entry['system']['memory']['percent'],
                'disk_percent': entry['system']['disk']['percent'],
                'network_sent': entry['system']['network']['bytes_sent'],
                'network_recv': entry['system']['network']['bytes_recv'],
            }
            
            # 添加应用响应时间
            if entry['application']['status'] == 'ok':
                record['response_time'] = entry['application']['response_time']
            else:
                record['response_time'] = np.nan
            
            # 添加进程指标（如果存在）
            if 'process' in entry:
                record['process_cpu'] = entry['process']['cpu_percent']
                record['process_memory'] = entry['process']['memory_percent']
                record['process_threads'] = entry['process']['threads_count']
            
            # 添加数据库指标（如果存在）
            if 'database' in entry and entry['database']['connected']:
                record['db_connection_time'] = entry['database'].get('connection_time', np.nan)
            
            df_data.append(record)
        
        # 创建DataFrame
        df = pd.DataFrame(df_data)
        df.set_index('timestamp', inplace=True)
        df.sort_index(inplace=True)
        
        return df
    
    def plot_system_metrics(self, df):
        """绘制系统级别指标图表"""
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        
        # 1. CPU使用率图表
        plt.figure()
        df['cpu_avg_load'].plot(title='CPU使用率随时间变化', 
                          color='blue', linewidth=2)
        plt.xlabel('时间')
        plt.ylabel('CPU使用率 (%)')
        plt.grid(True)
        plt.tight_layout()
        cpu_chart = os.path.join(self.output_dir, f'cpu_usage_{timestamp}.png')
        plt.savefig(cpu_chart)
        logger.info(f"CPU图表已保存至: {cpu_chart}")
        
        # 2. 内存使用率图表
        plt.figure()
        df['memory_percent'].plot(title='内存使用率随时间变化', 
                            color='green', linewidth=2)
        plt.xlabel('时间')
        plt.ylabel('内存使用率 (%)')
        plt.grid(True)
        plt.tight_layout()
        memory_chart = os.path.join(self.output_dir, f'memory_usage_{timestamp}.png')
        plt.savefig(memory_chart)
        logger.info(f"内存图表已保存至: {memory_chart}")
        
        # 3. 网络流量图表
        plt.figure()
        df['network_sent'] = df['network_sent'] / (1024*1024)  # 转换为MB
        df['network_recv'] = df['network_recv'] / (1024*1024)  # 转换为MB
        df[['network_sent', 'network_recv']].plot(title='网络流量随时间变化', 
                                           figsize=(12, 6))
        plt.xlabel('时间')
        plt.ylabel('流量 (MB)')
        plt.legend(['发送', '接收'])
        plt.grid(True)
        plt.tight_layout()
        network_chart = os.path.join(self.output_dir, f'network_traffic_{timestamp}.png')
        plt.savefig(network_chart)
        logger.info(f"网络流量图表已保存至: {network_chart}")
        
        # 4. 应用响应时间图表
        if 'response_time' in df.columns and not df['response_time'].isna().all():
            plt.figure()
            df['response_time'].plot(title='应用响应时间随时间变化', 
                               color='purple', linewidth=2)
            plt.xlabel('时间')
            plt.ylabel('响应时间 (秒)')
            plt.grid(True)
            plt.tight_layout()
            response_chart = os.path.join(self.output_dir, f'response_time_{timestamp}.png')
            plt.savefig(response_chart)
            logger.info(f"响应时间图表已保存至: {response_chart}")
        
        # 5. 如果有进程数据，绘制进程指标
        process_columns = ['process_cpu', 'process_memory', 'process_threads']
        if all(col in df.columns for col in process_columns):
            # 进程CPU和内存使用
            fig, ax1 = plt.subplots()
            
            color = 'tab:red'
            ax1.set_xlabel('时间')
            ax1.set_ylabel('CPU使用率 (%)', color=color)
            ax1.plot(df.index, df['process_cpu'], color=color)
            ax1.tick_params(axis='y', labelcolor=color)
            
            ax2 = ax1.twinx()
            color = 'tab:blue'
            ax2.set_ylabel('内存使用率 (%)', color=color)
            ax2.plot(df.index, df['process_memory'], color=color)
            ax2.tick_params(axis='y', labelcolor=color)
            
            plt.title('进程CPU和内存使用率')
            fig.tight_layout()
            process_chart = os.path.join(self.output_dir, f'process_usage_{timestamp}.png')
            plt.savefig(process_chart)
            logger.info(f"进程使用率图表已保存至: {process_chart}")
            
            # 进程线程数
            plt.figure()
            df['process_threads'].plot(title='进程线程数随时间变化', 
                                color='orange', linewidth=2)
            plt.xlabel('时间')
            plt.ylabel('线程数')
            plt.grid(True)
            plt.tight_layout()
            threads_chart = os.path.join(self.output_dir, f'process_threads_{timestamp}.png')
            plt.savefig(threads_chart)
            logger.info(f"进程线程数图表已保存至: {threads_chart}")
        
        # 6. 数据库连接时间图表
        if 'db_connection_time' in df.columns and not df['db_connection_time'].isna().all():
            plt.figure()
            df['db_connection_time'].plot(title='数据库连接时间随时间变化', 
                                    color='brown', linewidth=2)
            plt.xlabel('时间')
            plt.ylabel('连接时间 (秒)')
            plt.grid(True)
            plt.tight_layout()
            db_chart = os.path.join(self.output_dir, f'db_connection_time_{timestamp}.png')
            plt.savefig(db_chart)
            logger.info(f"数据库连接时间图表已保存至: {db_chart}")
        
        # 7. 综合性能图表：CPU、内存、磁盘使用率
        plt.figure()
        df[['cpu_avg_load', 'memory_percent', 'disk_percent']].plot(
            title='系统资源使用率随时间变化')
        plt.xlabel('时间')
        plt.ylabel('使用率 (%)')
        plt.legend(['CPU', '内存', '磁盘'])
        plt.grid(True)
        plt.tight_layout()
        system_chart = os.path.join(self.output_dir, f'system_usage_{timestamp}.png')
        plt.savefig(system_chart)
        logger.info(f"系统资源使用率图表已保存至: {system_chart}")
        
        return {
            'cpu': cpu_chart,
            'memory': memory_chart,
            'network': network_chart,
            'system': system_chart
        }
    
    def generate_html_report(self, chart_files, df):
        """生成HTML格式的报告"""
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        report_file = os.path.join(self.output_dir, f'performance_report_{timestamp}.html')
        
        # 计算一些统计指标
        stats = {
            'cpu_avg': df['cpu_avg_load'].mean(),
            'cpu_max': df['cpu_avg_load'].max(),
            'memory_avg': df['memory_percent'].mean(),
            'memory_max': df['memory_percent'].max(),
            'disk_avg': df['disk_percent'].mean(),
            'start_time': df.index.min(),
            'end_time': df.index.max(),
            'duration': (df.index.max() - df.index.min()).total_seconds() / 3600,  # 小时
            'samples': len(df)
        }
        
        # 如果有响应时间数据，添加相关统计
        if 'response_time' in df.columns and not df['response_time'].isna().all():
            stats['resp_avg'] = df['response_time'].mean()
            stats['resp_max'] = df['response_time'].max()
            stats['resp_min'] = df['response_time'].min()
        
        # 生成HTML内容
        html_content = f"""
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>系统性能监控报告</title>
            <style>
                body {{ font-family: Arial, sans-serif; margin: 20px; }}
                h1, h2 {{ color: #333; }}
                .summary {{ background-color: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; }}
                .chart-container {{ margin-bottom: 30px; }}
                .chart {{ max-width: 100%; border: 1px solid #ddd; border-radius: 5px; }}
                table {{ border-collapse: collapse; width: 100%; }}
                th, td {{ text-align: left; padding: 8px; border-bottom: 1px solid #ddd; }}
                th {{ background-color: #f2f2f2; }}
                .footer {{ margin-top: 30px; font-size: 0.8em; color: #777; }}
                .row {{ display: flex; flex-wrap: wrap; }}
                .column {{ flex: 50%; padding: 10px; }}
                @media screen and (max-width: 600px) {{ .column {{ flex: 100%; }} }}
            </style>
        </head>
        <body>
            <h1>系统性能监控报告</h1>
            <div class="summary">
                <h2>监控概要</h2>
                <table>
                    <tr><th>指标</th><th>数值</th></tr>
                    <tr><td>监控开始时间</td><td>{stats['start_time']}</td></tr>
                    <tr><td>监控结束时间</td><td>{stats['end_time']}</td></tr>
                    <tr><td>监控持续时间</td><td>{stats['duration']:.2f} 小时</td></tr>
                    <tr><td>采样数量</td><td>{stats['samples']}</td></tr>
                    <tr><td>平均CPU使用率</td><td>{stats['cpu_avg']:.2f}%</td></tr>
                    <tr><td>最大CPU使用率</td><td>{stats['cpu_max']:.2f}%</td></tr>
                    <tr><td>平均内存使用率</td><td>{stats['memory_avg']:.2f}%</td></tr>
                    <tr><td>最大内存使用率</td><td>{stats['memory_max']:.2f}%</td></tr>
                    <tr><td>平均磁盘使用率</td><td>{stats['disk_avg']:.2f}%</td></tr>
        """
        
        # 添加响应时间统计（如果有）
        if 'resp_avg' in stats:
            html_content += f"""
                    <tr><td>平均响应时间</td><td>{stats['resp_avg']:.4f} 秒</td></tr>
                    <tr><td>最大响应时间</td><td>{stats['resp_max']:.4f} 秒</td></tr>
                    <tr><td>最小响应时间</td><td>{stats['resp_min']:.4f} 秒</td></tr>
            """
        
        html_content += """
                </table>
            </div>
            
            <h2>性能图表</h2>
        """
        
        # 添加系统资源使用图表
        html_content += f"""
            <div class="chart-container">
                <h3>系统资源使用率</h3>
                <img class="chart" src="{os.path.basename(chart_files['system'])}" alt="系统资源使用率">
            </div>
            
            <div class="row">
                <div class="column">
                    <div class="chart-container">
                        <h3>CPU使用率</h3>
                        <img class="chart" src="{os.path.basename(chart_files['cpu'])}" alt="CPU使用率">
                    </div>
                </div>
                <div class="column">
                    <div class="chart-container">
                        <h3>内存使用率</h3>
                        <img class="chart" src="{os.path.basename(chart_files['memory'])}" alt="内存使用率">
                    </div>
                </div>
            </div>
            
            <div class="chart-container">
                <h3>网络流量</h3>
                <img class="chart" src="{os.path.basename(chart_files['network'])}" alt="网络流量">
            </div>
        """
        
        # 添加进程和响应时间图表（如果存在）
        for chart_type, chart_path in chart_files.items():
            if chart_type not in ['cpu', 'memory', 'network', 'system']:
                html_content += f"""
                <div class="chart-container">
                    <h3>{chart_type.replace('_', ' ').title()}</h3>
                    <img class="chart" src="{os.path.basename(chart_path)}" alt="{chart_type}">
                </div>
                """
        
        # 添加页脚
        html_content += f"""
            <div class="footer">
                <p>此报告由系统性能监控可视化工具生成于 {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
            </div>
        </body>
        </html>
        """
        
        # 写入HTML文件
        with open(report_file, 'w', encoding='utf-8') as f:
            f.write(html_content)
        
        logger.info(f"性能报告已保存至: {report_file}")
        return report_file
    
    def visualize(self):
        """执行可视化流程"""
        # 加载指标数据
        metrics = self.load_metrics()
        if not metrics:
            return False
        
        # 准备数据框
        df = self.prepare_dataframe(metrics)
        if df.empty:
            logger.error("处理后的数据为空")
            return False
        
        # 绘制图表
        chart_files = self.plot_system_metrics(df)
        
        # 生成HTML报告
        report_file = self.generate_html_report(chart_files, df)
        
        logger.info("可视化完成")
        return report_file

def main():
    parser = argparse.ArgumentParser(description='Django应用系统性能监控可视化工具')
    parser.add_argument('--input', help='指标数据文件路径')
    parser.add_argument('--output', help='图表输出目录')
    
    args = parser.parse_args()
    
    visualizer = MetricsVisualizer(
        metrics_file=args.input,
        output_dir=args.output
    )
    
    report_file = visualizer.visualize()
    if report_file:
        print(f"报告已生成：{report_file}")

if __name__ == "__main__":
    main() 